--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit ed822345bc78e649747519d9648bdbe5c6185cb0
Parents : bf8731c
Author : Ivan <e46112d44649266d71fe2193e00a4710>
Signature : T66BB85Valid, signed by author
Date : 2026-07-26T08:08:11-05:00
feat: update landlock sandbox by adding support for editable package installations and improve test coverage for LXMF message handling
Changes
9 files changed, 116 insertions(+), 73 deletions(-)
Diff
diff --git a/meshchatx.rsm b/meshchatx.rsm
index 377c95cb..313f5697 100644
Binary files a/meshchatx.rsm and b/meshchatx.rsm differ
diff --git a/meshchatx/src/backend/landlock_sandbox.py b/meshchatx/src/backend/landlock_sandbox.py
index 0a188df5..187595b8 100644
--- a/meshchatx/src/backend/landlock_sandbox.py
+++ b/meshchatx/src/backend/landlock_sandbox.py
@@ -340,6 +340,22 @@ def _collect_user_local_cli_roots() -> list[str]:
return paths
+def _collect_meshchatx_package_read_roots() -> list[str]:
+ """Editable installs may load sources outside sys.path entries."""
+ paths: list[str] = []
+ try:
+ import meshchatx as pkg
+ except ImportError:
+ return paths
+ init_file = getattr(pkg, "__file__", None)
+ if not init_file:
+ return paths
+ pkg_dir = _existing_dir(os.path.dirname(os.path.abspath(init_file)))
+ if pkg_dir and pkg_dir not in paths:
+ paths.append(pkg_dir)
+ return paths
+
+
def _collect_read_roots() -> list[str]:
roots = {
"/usr",
@@ -389,6 +405,8 @@ def _collect_read_roots() -> list[str]:
roots.add(prefix)
for root in _collect_user_local_cli_roots():
roots.add(root)
+ for root in _collect_meshchatx_package_read_roots():
+ roots.add(root)
return sorted(roots)
diff --git a/tests/backend/api_json_contract_schemas.py b/tests/backend/api_json_contract_schemas.py
index 754aba29..2afbea69 100644
--- a/tests/backend/api_json_contract_schemas.py
+++ b/tests/backend/api_json_contract_schemas.py
@@ -196,7 +196,7 @@ _SERVER_BIND_STATUS_SCHEMA: dict = {
"seccomp_requested": {"type": "boolean"},
"seccomp_auto_enabled": {"type": "boolean"},
"seccomp_disabled_by_env": {"type": "boolean"},
- "seccomp_active": {"type": "boolean"},
+ "seccomp_active": {"type": "boolean"},
}
_DEMO_PUBLIC_STATUS_FIELDS: dict = {
diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py
index 96140b19..731ede8b 100644
--- a/tests/backend/conftest.py
+++ b/tests/backend/conftest.py
@@ -25,6 +25,8 @@ from tests.backend.support.test_temp_dir import (
ensure_test_temp_dirs,
)
+pytest_plugins = ["tests.backend.lxmf_local_self_support"]
+
def _ensure_coverage_data_dir() -> None:
ensure_test_temp_dirs()
diff --git a/tests/backend/lxmf_local_self_support.py b/tests/backend/lxmf_local_self_support.py
new file mode 100644
index 00000000..ce1de745
--- /dev/null
+++ b/tests/backend/lxmf_local_self_support.py
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: 0BSD
+
+from unittest.mock import AsyncMock, MagicMock
+
+import LXMF
+import pytest
+
+LOCAL_LXMF = "a1" * 16
+REMOTE_PEER = "c3" * 16
+
+
+def fake_lxmf_message():
+ fake_lxm = MagicMock()
+ fake_lxm.fields = {}
+ fake_lxm.hash = MagicMock(hex=lambda: "d4" * 16)
+ fake_lxm.source_hash = MagicMock(hex=lambda: LOCAL_LXMF)
+ fake_lxm.destination_hash = MagicMock(hex=lambda: LOCAL_LXMF)
+ fake_lxm.incoming = False
+ fake_lxm.progress = 0
+ fake_lxm.delivery_attempts = 0
+ fake_lxm.next_delivery_attempt = None
+ fake_lxm.rssi = None
+ fake_lxm.snr = None
+ fake_lxm.q = None
+ fake_lxm.title = b""
+ fake_lxm.timestamp = 1_700_000_000
+ fake_lxm.state = LXMF.LXMessage.OUTBOUND
+ fake_lxm.method = LXMF.LXMessage.DIRECT
+ fake_lxm.get_fields = MagicMock(return_value={})
+ fake_lxm.content = ""
+ fake_lxm.include_ticket = False
+ return fake_lxm
+
+
+def lxmf_message_factory(*args, **kwargs):
+ msg = fake_lxmf_message()
+ if len(args) >= 3:
+ raw = args[2]
+ if isinstance(raw, bytes):
+ msg.content = raw
+ else:
+ msg.content = (raw or "").encode("utf-8")
+ return msg
+
+
+@pytest.fixture
+def local_self_app(mock_app):
+ ctx = mock_app.current_context
+ ctx.config.auto_send_failed_messages_to_propagation_node.set(False)
+ ctx.message_router.delivery_link_available.return_value = True
+ ctx.message_router.handle_outbound = MagicMock()
+ ctx.local_lxmf_destination = MagicMock()
+ ctx.local_lxmf_destination.hexhash = LOCAL_LXMF
+ ctx.local_lxmf_destination.hash = bytes.fromhex(LOCAL_LXMF)
+ mock_app.recall_identity = MagicMock(return_value=ctx.identity)
+ mock_app.get_current_icon_hash = MagicMock(return_value=None)
+ mock_app._await_transport_path = AsyncMock()
+ mock_app._convert_webm_opus_to_ogg = MagicMock(side_effect=lambda b: b)
+ mock_app.websocket_broadcast = AsyncMock()
+ if getattr(mock_app, "reticulum", None) is not None:
+ mock_app.reticulum.get_packet_rssi = MagicMock(return_value=None)
+ mock_app.reticulum.get_packet_snr = MagicMock(return_value=None)
+ mock_app.reticulum.get_packet_q = MagicMock(return_value=None)
+ return mock_app
diff --git a/tests/backend/test_csp_logic.py b/tests/backend/test_csp_logic.py
index 076c65cb..a20b8ad8 100644
--- a/tests/backend/test_csp_logic.py
+++ b/tests/backend/test_csp_logic.py
@@ -247,7 +247,7 @@ async def _csp_for_path(app_instance, path: str) -> str:
return web.Response(text="test")
routes = web.RouteTableDef()
- _, _, security_middleware, _, _, _, _ = app_instance._define_routes(routes)
+ _, _, security_middleware, _, _, _ = app_instance._define_routes(routes)
response = await security_middleware(request, mock_handler)
return response.headers.get("Content-Security-Policy", "")
diff --git a/tests/backend/test_landlock_sandbox.py b/tests/backend/test_landlock_sandbox.py
index 715968df..9f008211 100644
--- a/tests/backend/test_landlock_sandbox.py
+++ b/tests/backend/test_landlock_sandbox.py
@@ -108,6 +108,20 @@ def test_collect_read_roots_includes_user_local_cli_when_present():
), f"{local_bin!r} not covered by landlock read roots"
+def test_collect_read_roots_includes_meshchatx_package_dir():
+ roots = ll._collect_read_roots()
+ import meshchatx as pkg
+
+ init_file = getattr(pkg, "__file__", None)
+ assert init_file
+ pkg_dir = os.path.realpath(os.path.dirname(os.path.abspath(init_file)))
+ assert any(
+ pkg_dir == os.path.realpath(root)
+ or pkg_dir.startswith(os.path.realpath(root).rstrip("/") + "/")
+ for root in roots
+ ), f"meshchatx package dir {pkg_dir!r} not covered by read roots"
+
+
def test_collect_read_roots_includes_interpreter_prefix():
roots = ll._collect_read_roots()
exe = os.path.realpath(sys.executable)
diff --git a/tests/backend/test_lxmf_local_self_message.py b/tests/backend/test_lxmf_local_self_message.py
index 9bcf9ffd..d624d2ae 100644
--- a/tests/backend/test_lxmf_local_self_message.py
+++ b/tests/backend/test_lxmf_local_self_message.py
@@ -1,67 +1,14 @@
# SPDX-License-Identifier: 0BSD
-from unittest.mock import AsyncMock, MagicMock, patch
+from unittest.mock import MagicMock, patch
-import LXMF
import pytest
-LOCAL_LXMF = "a1" * 16
-REMOTE_PEER = "c3" * 16
-
-
-def _fake_lxmf_message():
- fake_lxm = MagicMock()
- fake_lxm.fields = {}
- fake_lxm.hash = MagicMock(hex=lambda: "d4" * 16)
- fake_lxm.source_hash = MagicMock(hex=lambda: LOCAL_LXMF)
- fake_lxm.destination_hash = MagicMock(hex=lambda: LOCAL_LXMF)
- fake_lxm.incoming = False
- fake_lxm.progress = 0
- fake_lxm.delivery_attempts = 0
- fake_lxm.next_delivery_attempt = None
- fake_lxm.rssi = None
- fake_lxm.snr = None
- fake_lxm.q = None
- fake_lxm.title = b""
- fake_lxm.timestamp = 1_700_000_000
- fake_lxm.state = LXMF.LXMessage.OUTBOUND
- fake_lxm.method = LXMF.LXMessage.DIRECT
- fake_lxm.get_fields = MagicMock(return_value={})
- fake_lxm.content = ""
- fake_lxm.include_ticket = False
- return fake_lxm
-
-
-def _lxmf_message_factory(*args, **kwargs):
- msg = _fake_lxmf_message()
- if len(args) >= 3:
- raw = args[2]
- if isinstance(raw, bytes):
- msg.content = raw
- else:
- msg.content = (raw or "").encode("utf-8")
- return msg
-
-
-@pytest.fixture
-def local_self_app(mock_app):
- ctx = mock_app.current_context
- ctx.config.auto_send_failed_messages_to_propagation_node.set(False)
- ctx.message_router.delivery_link_available.return_value = True
- ctx.message_router.handle_outbound = MagicMock()
- ctx.local_lxmf_destination = MagicMock()
- ctx.local_lxmf_destination.hexhash = LOCAL_LXMF
- ctx.local_lxmf_destination.hash = bytes.fromhex(LOCAL_LXMF)
- mock_app.recall_identity = MagicMock(return_value=ctx.identity)
- mock_app.get_current_icon_hash = MagicMock(return_value=None)
- mock_app._await_transport_path = AsyncMock()
- mock_app._convert_webm_opus_to_ogg = MagicMock(side_effect=lambda b: b)
- mock_app.websocket_broadcast = AsyncMock()
- if getattr(mock_app, "reticulum", None) is not None:
- mock_app.reticulum.get_packet_rssi = MagicMock(return_value=None)
- mock_app.reticulum.get_packet_snr = MagicMock(return_value=None)
- mock_app.reticulum.get_packet_q = MagicMock(return_value=None)
- return mock_app
+from tests.backend.lxmf_local_self_support import (
+ LOCAL_LXMF,
+ REMOTE_PEER,
+ lxmf_message_factory,
+)
def test_is_self_lxmf_destination_by_lxmf_hash(local_self_app):
@@ -86,7 +33,7 @@ async def test_send_to_self_persists_delivered_local_without_router(local_self_a
with (
patch("meshchatx.meshchat.RNS.Destination", return_value=fake_destination),
- patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=_lxmf_message_factory),
+ patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=lxmf_message_factory),
patch(
"meshchatx.src.backend.lxmf_utils.lxmf_message_solving_stamps",
return_value=False,
@@ -120,7 +67,7 @@ async def test_send_to_self_by_identity_hash(local_self_app):
with (
patch("meshchatx.meshchat.RNS.Destination", return_value=fake_destination),
- patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=_lxmf_message_factory),
+ patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=lxmf_message_factory),
patch(
"meshchatx.src.backend.lxmf_utils.lxmf_message_solving_stamps",
return_value=False,
diff --git a/tests/backend/test_lxmf_local_self_oracle.py b/tests/backend/test_lxmf_local_self_oracle.py
index fe78526d..7ab44ce5 100644
--- a/tests/backend/test_lxmf_local_self_oracle.py
+++ b/tests/backend/test_lxmf_local_self_oracle.py
@@ -13,15 +13,13 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
-pytest_plugins = ["tests.backend.test_lxmf_local_self_message"]
-
from meshchatx.meshchat import ReticulumMeshChat
from meshchatx.src.backend import reticulum_pathfinding
-from tests.backend.test_lxmf_local_self_message import (
+from tests.backend.lxmf_local_self_support import (
LOCAL_LXMF,
REMOTE_PEER,
- _fake_lxmf_message,
- _lxmf_message_factory,
+ fake_lxmf_message,
+ lxmf_message_factory,
)
@@ -71,7 +69,7 @@ async def test_local_self_sqlite_row_delivered_local(local_self_app):
with (
patch("meshchatx.meshchat.RNS.Destination", return_value=fake_destination),
- patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=_lxmf_message_factory),
+ patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=lxmf_message_factory),
patch(
"meshchatx.src.backend.lxmf_utils.lxmf_message_solving_stamps",
return_value=False,
@@ -99,7 +97,7 @@ async def test_local_self_conversation_summary_updated(local_self_app):
with (
patch("meshchatx.meshchat.RNS.Destination", return_value=fake_destination),
- patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=_lxmf_message_factory),
+ patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=lxmf_message_factory),
patch(
"meshchatx.src.backend.lxmf_utils.lxmf_message_solving_stamps",
return_value=False,
@@ -136,7 +134,7 @@ async def test_remote_peer_still_invokes_mesh_outbound(local_self_app):
with (
patch("meshchatx.meshchat.RNS.Destination", return_value=fake_destination),
- patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=_lxmf_message_factory),
+ patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=lxmf_message_factory),
patch(
"meshchatx.src.backend.lxmf_utils.lxmf_message_solving_stamps",
return_value=False,
@@ -155,7 +153,7 @@ async def test_remote_peer_still_invokes_mesh_outbound(local_self_app):
def test_db_upsert_override_writes_local_method(local_self_app):
app = local_self_app
- msg = _fake_lxmf_message()
+ msg = fake_lxmf_message()
msg.content = b"stored"
ReticulumMeshChat.db_upsert_lxmf_message(
app,
@@ -180,7 +178,7 @@ async def test_local_self_websocket_payload_is_delivered_local(local_self_app):
with (
patch("meshchatx.meshchat.RNS.Destination", return_value=fake_destination),
- patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=_lxmf_message_factory),
+ patch("meshchatx.meshchat.LXMF.LXMessage", side_effect=lxmf_message_factory),
patch(
"meshchatx.src.backend.lxmf_utils.lxmf_message_solving_stamps",
return_value=False,
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────